/**
 * 
 */
package org.lex.swing;

import java.awt.Component;
import java.awt.Font;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Formatter;

import javax.swing.Action;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;

import org.lex.swing.fontchooser.JFontChooser;


/**
 * @author Daniel Liu
 */
public class TextComponentHelper {
	private static Action[] COMMON_EDIT_ACTIONS = null;
	private static final String NULL = "null";
	private static final String APPENDABLE_KEY = TextComponentHelper.class.getCanonicalName() + " appendable";
	private static final String FORMATTER_KEY = TextComponentHelper.class.getCanonicalName() + " formatter";

	public static synchronized Formatter getFormatter(final JTextComponent tc) {
		Formatter instance = (Formatter) tc.getClientProperty(FORMATTER_KEY);
		if (null == instance) {
			instance = new Formatter(TextComponentHelper.getAppendable(tc));
			tc.putClientProperty(FORMATTER_KEY, instance);
		}
		return instance;
	}

	public static synchronized Appendable getAppendable(final JTextComponent tc) {
		/*
		 * we put this Appendable in clientProperty, so there will be always at
		 * most only one instance of Appendable changing this JTextComponent
		 */
		Appendable instance = (Appendable) tc.getClientProperty(APPENDABLE_KEY);
		if (null == instance) {
			instance = new Appendable() {
				@Override
				public Appendable append(char c) {
					TextComponentHelper.append(tc, String.valueOf(c));
					return this;
				}

				@Override
				public Appendable append(CharSequence csq, int start, int end) {
					if (null == csq)
						TextComponentHelper.append(tc, NULL);
					else
						TextComponentHelper.append(tc, csq.subSequence(start, end));
					return this;
				}

				@Override
				public Appendable append(CharSequence csq) {
					if (null == csq)
						csq = NULL;
					return this.append(csq, 0, csq.length());
				}
			};
			tc.putClientProperty(APPENDABLE_KEY, instance);
		}
		return instance;
	}

	/**
	 * Appends the given text to the end of the JTextComponent. Does nothing if
	 * the document model is null or the string is null or empty.
	 */
	public static synchronized JTextComponent append(JTextComponent tc, CharSequence text) {
		Document doc = tc.getDocument();
		if (null == doc)
			return tc;
		try {
			doc.insertString(doc.getLength(), String.valueOf(text), null);
		} catch (BadLocationException e) {
		}
		return tc;
	}

	public static void addCommonPopupMenu(JTextArea textArea) {
		TextComponentHelper.addCommonEditMenu(textArea);
		TextComponentHelper.addWrapMenu(textArea);
	}

	public static JPopupMenu addWrapMenu(final JTextArea textArea) {
		JPopupMenu popup = textArea.getComponentPopupMenu();
		if (null == popup) {
			popup = new JPopupMenu();
			textArea.setComponentPopupMenu(popup);
		} else
			popup.addSeparator();
		final ActionBuilder wrapWordAction = new ActionBuilder("Wrap Word");
		wrapWordAction.setMnemonic('W');
		wrapWordAction.setActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				boolean b = wrapWordAction.isSelected();
				textArea.setWrapStyleWord(b);
				System.out.println("WrapWord: " + b);
			}
		});
		final ActionBuilder wrapLineAction = new ActionBuilder("Wrap Line");
		wrapLineAction.setMnemonic('L');
		wrapLineAction.setActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				boolean b = wrapLineAction.isSelected();
				textArea.setLineWrap(b);
				wrapWordAction.setEnabled(b);
				System.out.println("WrapLine: " + b);
			}
		});
		wrapLineAction.setSelected(textArea.getLineWrap());
		popup.add(new JCheckBoxMenuItem(wrapLineAction));
		popup.add(new JCheckBoxMenuItem(wrapWordAction));
		return popup;
	}

	public static JPopupMenu addCommonEditMenu(JTextComponent tc) {
		JPopupMenu popup = tc.getComponentPopupMenu();
		boolean sep = true;
		if (null == popup) {
			sep = false;
			popup = new JPopupMenu();
			tc.setComponentPopupMenu(popup);
		}
		return PopupMenuHelper.insertFront(popup, getCommonEditActions(), sep);
	}

	public static Action[] getCommonEditActions() {
		if (null == COMMON_EDIT_ACTIONS) {
			Action[] actions = { new DefaultEditorKit.CutAction(), new DefaultEditorKit.CopyAction(), new DefaultEditorKit.PasteAction() };
			String[] names = { "Cut", "Copy", "Paste" };
			int[] mns = { KeyEvent.VK_T, KeyEvent.VK_C, KeyEvent.VK_P };
			for (int i = 0; i < actions.length; i++)
				ActionHelper.set(actions[i], names[i], mns[i], null);
			COMMON_EDIT_ACTIONS = actions;
		}
		return COMMON_EDIT_ACTIONS;
	}

	// static class TextAreaWrapHandler implements PropertyChangeListener
	// {
	// final JTextArea textArea;
	// ActionBuilder wrapLineAction = null;
	// ActionBuilder wrapWordAction = null;
	//
	// TextAreaWrapHandler( JTextArea textArea )
	// {
	// this.textArea = textArea;
	// this.textArea.addPropertyChangeListener( this );
	// boolean wrapLine = textArea.getLineWrap();
	// this.getWrapLineAction().setSelected( wrapLine );
	// this.getWrapWordAction().setSelected( textArea.getWrapStyleWord() );
	// this.wrapWordAction.setEnabled( wrapLine );
	// }
	//
	// ActionBuilder getWrapLineAction()
	// {
	// if( null == wrapLineAction )
	// {
	// wrapLineAction = new ActionBuilder( "Wrap Line" );
	// wrapLineAction.setMnemonic( 'L' );
	// wrapLineAction.setActionListener( new ActionListener(){
	// public void actionPerformed( ActionEvent e )
	// {
	// boolean b = wrapLineAction.isSelected();
	// textArea.setLineWrap( b );
	// getWrapWordAction().setEnabled( b );
	// }
	// } );
	// }
	// return wrapLineAction;
	// }
	//
	// ActionBuilder getWrapWordAction()
	// {
	// if( null == wrapWordAction )
	// {
	// wrapWordAction = new ActionBuilder( "Wrap Word" );
	// wrapWordAction.setMnemonic( 'W' );
	// wrapWordAction.setActionListener( new ActionListener(){
	// public void actionPerformed( ActionEvent e )
	// {
	// textArea.setWrapStyleWord( wrapWordAction.isSelected() );
	// }
	// } );
	// }
	// return wrapWordAction;
	// }
	//
	// public void propertyChange( PropertyChangeEvent e )
	// {
	// String property = e.getPropertyName();
	// System.out.println( property + ": " + e.getOldValue() + " --> " +
	// e.getNewValue() );
	// Object newValue = e.getNewValue();
	// if( "lineWrap".equals( property ) )
	// this.wrapLine( (Boolean) newValue );
	// else if( "wrapStyleWord".equals( property ) )
	// this.wrapWord( (Boolean) newValue );
	// }
	//
	// private void wrapLine( boolean b )
	// {
	// this.getWrapLineAction().setSelected( b );
	// }
	//
	// private void wrapWord( boolean b )
	// {
	// this.getWrapWordAction().setSelected( b );
	// }
	// }
	private TextComponentHelper() {
	}

	public static class LineWrapAction extends ActionBuilder {
		private final JTextArea textArea;

		public LineWrapAction(JTextArea textArea) {
			this(textArea, "Wrap Line");
		}

		public LineWrapAction(JTextArea textArea, String text) {
			super();
			this.textArea = textArea;
			this.setName(text);
			this.setSelected(textArea.getLineWrap());
		}

		public void actionPerformed(ActionEvent e) {
			boolean selected = this.isSelected();
			textArea.setLineWrap(selected);
		}
	}

	public static class FontAction extends ActionBuilder {
		private final Component component;

		public FontAction(Component component) {
			this(component, "Font...");
		}

		public FontAction(Component component, String text) {
			super();
			this.component = component;
			this.setName(text);
		}

		public void actionPerformed(ActionEvent e) {
			Font font = JFontChooser.showDialog(component, "Font", component.getFont());
			if (null != font)
				component.setFont(font);
		}
	}

	public static class TabSizeAction extends ActionBuilder {
		private final JTextArea textArea;

		public TabSizeAction(JTextArea textArea) {
			this(textArea, "Tab size...");
		}

		public TabSizeAction(JTextArea textArea, String text) {
			this.setName(text);
			this.textArea = textArea;
		}

		public void actionPerformed(ActionEvent e) {
			int old = textArea.getTabSize();
			Object value = JOptionPane.showInputDialog(root(), "Set the tab size:", "Tab Size", JOptionPane.QUESTION_MESSAGE, null, null, old);
			if (null == value)
				return;
			int size = 2;
			try {
				size = Integer.parseInt(String.valueOf(value));
			} catch (Exception ex) {
				showError("Error", ex.getLocalizedMessage());
				return;
			}
			if (size < 0) {
				showError("Error", "Tab size should be a positive number.");
				return;
			}
			textArea.setTabSize(size);
		}

		private void showError(String title, String message) {
			JOptionPane.showMessageDialog(root(), message, title, JOptionPane.ERROR_MESSAGE);
		}

		private Window root() {
			return SwingUtilities.getWindowAncestor(textArea);
		}
	}
}
